﻿// .h include
#include "wthttp.h"

// Qt lib import
#include <QCoreApplication>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QQmlApplicationEngine>
#include <QEventLoop>
#include <QMutex>
#include <QMap>
#include <QThread>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QNetworkProxy>
#include <QHttpMultiPart>
#include <QFileInfo>

// WTHttpRequest
WTHttpRequest::WTHttpRequest():
    QObject( WTHttp::networkAccessManager() )
{
    timeoutTimer_.setSingleShot( true );
    connect( &timeoutTimer_, &QTimer::timeout, this, &WTHttpRequest::onRequestTimeout );
}

WTHttpRequest::~WTHttpRequest()
{
    if ( reply_ )
    {
        reply_->deleteLater();
        reply_ = nullptr;
    }
}

QPointer< WTHttpRequest > WTHttpRequest::create()
{
    QPointer< WTHttpRequest > result( new WTHttpRequest );

    return result;
}

WTHttpRequest &WTHttpRequest::onSucceed(const std::function< void(const QPointer< WTHttpRequest > &request) > &callback)
{
    succeedCallback_ = [ = ]()
    {
        callback( this );
    };

    return *this;
}

WTHttpRequest &WTHttpRequest::onSucceed(const std::function< void() > &callback)
{
    succeedCallback_ = callback;

    return *this;
}

WTHttpRequest &WTHttpRequest::onError(const std::function< void(const QPointer< WTHttpRequest > &request) > &callback)
{
    errorCallback_ = [ = ]()
    {
        callback( this );
    };

    return *this;
}

WTHttpRequest &WTHttpRequest::onError(const std::function< void() > &callback)
{
    errorCallback_ = callback;

    return *this;
}

WTHttpRequest &WTHttpRequest::onTimeout(const std::function< void(const QPointer< WTHttpRequest > &request) > &callback)
{
    timeoutCallback_ = [ = ]()
    {
        callback( this );
    };

    return *this;
}

WTHttpRequest &WTHttpRequest::onTimeout(const std::function< void() > &callback)
{
    timeoutCallback_ = callback;

    return *this;
}

void WTHttpRequest::wait()
{
    QEventLoop eventLoop;

    auto succeedCallback = succeedCallback_;
    auto errorCallback   = errorCallback_;

    succeedCallback_ = [ & ]()
    {
        if ( succeedCallback ) { succeedCallback(); }
        eventLoop.quit();
    };

    errorCallback_ = [ & ]()
    {
        if ( errorCallback ) { errorCallback(); }
        eventLoop.quit();
    };

    eventLoop.exec( QEventLoop::ExcludeUserInputEvents );
}

QObject *WTHttpRequest::get()
{
    if ( request_.url().isEmpty() )
    {
        request_ = WTHttp::defaultNetworkRequest( url() );
    }

    if ( this->isRunning() ) { qDebug() << "WTHttpRequest::get: already running"; return this; }

    if ( this->url().isEmpty() ) { qDebug() << "WTHttpRequest::get: url is empty"; return this; }

    if ( !this->requestHeaderProvider( request_ ) ) { qDebug() << "WTHttpRequest::get: requestHeaderProvider error"; return this; }

    if ( !this->requestUrlSplicer( request_ ) ) { qDebug() << "WTHttpRequest::get: requestUrlSplicer error"; return this; }

    if ( enabledLog() )
    {
        qDebug() << "WTHttpRequest: GET:" << request_.url().toString().toUtf8().constData();
    }

    handle( WTHttp::networkAccessManager()->get( request_ ) );

    return this;
}

QObject *WTHttpRequest::get(const QVariant &request)
{
    if ( request.canConvert< QNetworkRequest >() )
    {
        return get( request.value< QNetworkRequest >() );
    }
    else if ( request.canConvert< QUrl >() )
    {
        return get( request.value< QUrl >() );
    }
    else
    {
        qDebug() << "WTHttpRequest::get: unsupport data type:" << request;
        return this;
    }
}

QObject *WTHttpRequest::get(const QNetworkRequest &request)
{
    this->setUrl( request.url().toString() );

    request_ = request;

    return get();
}

QObject *WTHttpRequest::get(const QString &url)
{
    this->setUrl( url );

    request_ = WTHttp::defaultNetworkRequest( url );

    return get();
}

QObject *WTHttpRequest::get(const char *url)
{
    return get( QString( url ) );
}

QObject *WTHttpRequest::upload(const QString &url, const QString &file)
{

    this->setUrl( url );

    request_ = WTHttp::defaultNetworkRequest( url ,false);

    if ( this->isRunning() ) { qDebug() << "WTHttpRequest::post: already running"; return this; }

    if ( this->url().isEmpty() ) { qDebug() << "WTHttpRequest::post: url is empty"; return this; }

    if ( !this->requestUrlSplicer( request_ ) ) { qDebug() << "WTHttpRequest::get: requestUrlSplicer error"; return this; }
    QHttpMultiPart* multiPart=new QHttpMultiPart(QHttpMultiPart::FormDataType);
    QHttpPart imagePart;
    QFileInfo finfo(file);
    QString tmp = QString("file/%1").arg(finfo.suffix());
    imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(tmp));
//    imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpg"));
    imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"" + finfo.fileName() + "\""));
    QFile f(file);
    f.open(QFile::ReadOnly);
    QByteArray body=f.readAll();
    f.close();
    imagePart.setBody(body);
    qDebug()<<"=====upload======"<<url<<file<<body.size();
    multiPart->append(imagePart);
    handle( WTHttp::networkAccessManager()->post( request_, multiPart ) );
}

QObject *WTHttpRequest::post()
{
    if ( request_.url().isEmpty() )
    {
        request_ = WTHttp::defaultNetworkRequest( url() );
    }

    if ( this->isRunning() ) { qDebug() << "WTHttpRequest::post: already running"; return this; }

    if ( this->url().isEmpty() ) { qDebug() << "WTHttpRequest::post: url is empty"; return this; }

    QByteArray data;

    if ( !this->requestDataSerializer( data ) ) { qDebug() << "WTHttpRequest::post: requestDataSerializer error"; return this; }

    if ( !this->requestHeaderProvider( request_ ) ) { qDebug() << "WTHttpRequest::post: requestHeaderProvider error"; return this; }

    if ( !this->requestUrlSplicer( request_ ) ) { qDebug() << "WTHttpRequest::post: requestUrlSplicer error"; return this; }

    if ( enabledLog() )
    {
        qDebug() << "WTHttpRequest: POST:" << request_.url().toString().toUtf8().constData();
        qDebug() << "WTHttpRequest: body:" << ( ( data.size() < 2048 ) ? ( data ) : ( data.mid( 0, 2048 ) ) ).constData();
    }

    handle( WTHttp::networkAccessManager()->post( request_, data ) );

    return this;
}

QObject *WTHttpRequest::post(const QVariant &request, const QVariant &data)
{
    if ( request.canConvert< QNetworkRequest >() )
    {
        return post( request.value< QNetworkRequest >(), data );
    }
    else if ( request.canConvert< QUrl >() )
    {
        return post( request.value< QUrl >(), data );
    }
    else
    {
        qDebug() << "WTHttpRequest::post: unsupport data type:" << request;
        return this;
    }
}

QObject *WTHttpRequest::post(const QNetworkRequest &request, const QVariant &data)
{
    this->setUrl( request.url().toString() );
    this->setRequestData( data );

    request_ = request;

    return post();
}

QObject *WTHttpRequest::post(const QString &url, const QVariant &data)
{
    this->setUrl( url );
    this->setRequestData( data );

    request_ = WTHttp::defaultNetworkRequest( url );

    return post();
}

QObject *WTHttpRequest::post(const char *url, const QVariant &data)
{
//    qDebug()<<"==========="<<url;
    return post( QString( url ), data );
}

QObject *WTHttpRequest::put()
{
    if ( request_.url().isEmpty() )
    {
        request_ = WTHttp::defaultNetworkRequest( url() );
    }

    if ( this->isRunning() ) { qDebug() << "WTHttpRequest::put: already running"; return this; }

    if ( this->url().isEmpty() ) { qDebug() << "WTHttpRequest::put: url is empty"; return this; }

    QByteArray data;

    if ( !this->requestDataSerializer( data ) ) { qDebug() << "WTHttpRequest::put: requestDataSerializer error"; return this; }

    if ( !this->requestHeaderProvider( request_ ) ) { qDebug() << "WTHttpRequest::put: requestHeaderProvider error"; return this; }

    if ( !this->requestUrlSplicer( request_ ) ) { qDebug() << "WTHttpRequest::put: requestUrlSplicer error"; return this; }

    if ( enabledLog() )
    {
        qDebug() << "WTHttpRequest: PUT:" << request_.url().toString().toUtf8().constData();
        qDebug() << "WTHttpRequest: body:" << ( ( data.size() < 2048 ) ? ( data ) : ( data.mid( 0, 2048 ) ) ).constData();
    }

    handle( WTHttp::networkAccessManager()->put( request_, data ) );

    return this;
}

QObject *WTHttpRequest::put(const QVariant &request, const QVariant &data)
{
    if ( request.canConvert< QNetworkRequest >() )
    {
        return put( request.value< QNetworkRequest >(), data );
    }
    else if ( request.canConvert< QUrl >() )
    {
        return put( request.value< QUrl >(), data );
    }
    else
    {
        qDebug() << "WTHttpRequest::put: unsupport data type:" << request;
        return this;
    }
}

QObject *WTHttpRequest::put(const QNetworkRequest &request, const QVariant &data)
{
    this->setUrl( request.url().toString() );
    this->setRequestData( data );

    request_ = request;

    return put();
}

QObject *WTHttpRequest::put(const QString &url, const QVariant &data)
{
    this->setUrl( url );
    this->setRequestData( data );

    request_ = WTHttp::defaultNetworkRequest( url );

    return put();
}

QObject *WTHttpRequest::put(const char *url, const QVariant &data)
{
    return put( QString( url ), data );
}

void WTHttpRequest::clear()
{
    succeedCallback_ = { };
    errorCallback_   = { };
    timeoutCallback_ = { };

    if ( reply_ && reply_->isRunning() )
    {
        reply_->abort();
    }

    timeoutTimer_.stop();

    this->deleteLater();
}

QObject *WTHttpRequest::onSucceed(QJSValue callback)
{
    QSharedPointer< QJSValue > value( new QJSValue( callback ) );

    succeedCallback_ = [ = ]()
    {
        if ( !WTHttp::qmlEngine() )
        {
            qDebug() << "WTHttpRequest::onSucceed: qmlEngine is null";
            return;
        }

        if ( value->isCallable() )
        {
            value->call( { WTHttp::qmlEngine()->newQObject( this ) } );
        }
    };

    return this;
}

QObject *WTHttpRequest::onError(QJSValue callback)
{
    QSharedPointer< QJSValue > value( new QJSValue( callback ) );

    errorCallback_ = [ = ]()
    {
        if ( !WTHttp::qmlEngine() )
        {
            qDebug() << "WTHttpRequest::onSucceed: qmlEngine is null";
            return;
        }

        if ( value->isCallable() )
        {
            value->call( { WTHttp::qmlEngine()->newQObject( this ) } );
        }
    };

    return this;
}

QObject *WTHttpRequest::onTimeout(QJSValue callback)
{
    QSharedPointer< QJSValue > value( new QJSValue( callback ) );

    timeoutCallback_ = [ = ]()
    {
        if ( !WTHttp::qmlEngine() )
        {
            qDebug() << "WTHttpRequest::onSucceed: qmlEngine is null";
            return;
        }

        if ( value->isCallable() )
        {
            value->call( { WTHttp::qmlEngine()->newQObject( this ) } );
        }
    };

    return this;
}

bool WTHttpRequest::requestHeaderProvider(
    QNetworkRequest &request )
{
    Q_UNUSED( request );

    return true;
}

bool WTHttpRequest::requestUrlSplicer(
    QNetworkRequest &request )
{
    Q_UNUSED( request );

    return true;
}

bool WTHttpRequest::requestDataSerializer(
    QByteArray &data )
{
    if ( requestData_.isNull() )
    {
        data.clear();
    }
    else if ( requestData_.canConvert< QJsonObject >() )
    {
        data = QJsonDocument( requestData_.toJsonObject() ).toJson( QJsonDocument::Compact );
    }
    else if ( requestData_.canConvert< QJsonArray >() )
    {
        data = QJsonDocument( requestData_.toJsonArray() ).toJson( QJsonDocument::Compact );
    }
    else if ( requestData_.canConvert< QVariantMap >() )
    {
        data = QJsonDocument( QJsonObject::fromVariantMap( requestData_.toMap() ) ).toJson( QJsonDocument::Compact );
    }
    else if ( requestData_.canConvert< QVariantList >() )
    {
        data = QJsonDocument( QJsonArray::fromVariantList( requestData_.toList() ) ).toJson( QJsonDocument::Compact );
    }
    else if ( requestData_.canConvert< QByteArray >() )
    {
        data = requestData_.toByteArray();
    }
    else if ( requestData_.canConvert< QString >() )
    {
        data = requestData_.toString().toUtf8();
    }
    else
    {
        qDebug() << "WTHttpRequest::requestDataSerializer: unsupport data type:" << requestData_;
        data.clear();
    }

    return true;
}

bool WTHttpRequest::responseDataParser(
    const QPointer< QNetworkReply > &reply,
    const QByteArray &               sourceData,
    QVariant &                       targetData,
    QString &                        message,int & code )
{
    Q_UNUSED( reply );

    targetData = sourceData;
    message.clear();

    return true;
}

void WTHttpRequest::handle(const QPointer< QNetworkReply > &reply)
{
    reply_ = reply;

    QObject::connect( reply, &QNetworkReply::uploadProgress, std::bind( &WTHttpRequest::onUploadProgress, this, std::placeholders::_1, std::placeholders::_2 ) );
    QObject::connect( reply, &QNetworkReply::downloadProgress, std::bind( &WTHttpRequest::onDownloadProgress, this, std::placeholders::_1, std::placeholders::_2 ) );
    QObject::connect( reply, &QNetworkReply::errorOccurred, std::bind( &WTHttpRequest::onRequestError, this ) );
    QObject::connect( reply, &QNetworkReply::finished, std::bind( &WTHttpRequest::onRequestFinished, this ) );

    timeoutTimer_.stop();
    timeoutTimer_.start( this->timeoutInterval() );

    requestTimer_.restart();

    this->setIsRunning( true );
}

void WTHttpRequest::onUploadProgress(const qint64 bytesReceived, const qint64 bytesTotal)
{
    timeoutTimer_.stop();
    timeoutTimer_.start( this->timeoutInterval() );

    emit this->uploadProgress( bytesReceived, bytesTotal );
}

void WTHttpRequest::onDownloadProgress(const qint64 bytesReceived, const qint64 bytesTotal)
{
    timeoutTimer_.stop();
    timeoutTimer_.start( this->timeoutInterval() );

    emit this->downloadProgress( bytesReceived, bytesTotal );
}

void WTHttpRequest::onRequestFinished()
{
    if ( !this->isRunning() ) { return; }
    this->setIsRunning( false );

    if ( !reply_ )
    {
        qDebug() << "WTHttpRequest::onRequestFinished: reply is null";
        this->deleteLater();
        return;
    }

    QByteArray sourceData = reply_->readAll();
    if ( enabledLog() )
    {
        qDebug() << "WTHttpRequest: request message:" << sourceData;
    }
    QVariant   targetData;
    QString    message;
    int code=0;
    const auto isSucceed = responseDataParser( reply_, sourceData, targetData, message ,code);
    this->setResponseCode(code);
    if ( isSucceed )
    {
        if ( enabledLog() )
        {
            qDebug() << "WTHttpRequest: request succeed, response:" <<  QString(sourceData);
        }

        this->setRequestTimeElapsed( static_cast< int >( requestTimer_.elapsed() ) );
        this->setResponseData( targetData );
        this->setIsSucceed( true );

        this->doSucceedCallback();
        this->deleteLater();
    }
    else
    {
        if ( enabledLog() )
        {
            qDebug() << "WTHttpRequest: request fail, message:" << message.toUtf8().constData() << ", data:" << ( ( sourceData.size() < 2048 ) ? sourceData : ( sourceData.mid( 0, 2048 ) ) ).constData();
            qDebug() << "WTHttpRequest: request fail, data:" << QString(sourceData);


        }
        this->setErrorMessage( message );

        this->doErrorCallback();
        this->deleteLater();
    }
}

void WTHttpRequest::onRequestError()
{
    if ( !this->isRunning() ) { return; }
    this->setIsRunning( false );

    if ( !reply_ )
    {
        qDebug() << "WTHttpRequest::onRequestError: reply is null";
        this->deleteLater();
        return;
    }

    this->setResponseData( reply_->readAll() );

    if ( reply_->errorString().isEmpty() )
    {
        this->setErrorMessage( QString( "http code: %1" ).arg( QString::number( reply_->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() ) ) );
    }
    else
    {
        this->setErrorMessage( reply_->errorString() );
    }

    this->doErrorCallback();
    this->deleteLater();
}

void WTHttpRequest::onRequestTimeout()
{
    if ( !this->isRunning() ) { return; }
    this->setIsRunning( false );

    if ( !reply_ )
    {
        qDebug() << "WTHttpRequest::onRequestTimeout: reply is null";
        this->deleteLater();
        return;
    }

    if ( reply_ && reply_->isRunning() )
    {
        reply_->abort();
    }

//    this->setErrorMessage( "Request timeout" );

    this->doTimeoutCallback();
    this->deleteLater();
}

void WTHttpRequest::doSucceedCallback()
{
    emit this->succeed();

    if ( succeedCallback_ )
    {
        succeedCallback_();
    }
}

void WTHttpRequest::doErrorCallback()
{
    emit this->error();

    if ( errorCallback_ )
    {
        errorCallback_();
    }
}

void WTHttpRequest::doTimeoutCallback()
{
    emit this->timeout();

    if ( timeoutCallback_ )
    {
        timeoutCallback_();
    }

    this->doErrorCallback();
}


// WTHttp
struct WTHttpRuntimeData
{
    QQmlEngine *engine;

    QMutex mutex;
    QMap< QThread *, QPointer< QNetworkAccessManager > > networkAccessManagerMap; // thread -> manager
};

WTHttpRuntimeData *runtimeData()
{
    static WTHttpRuntimeData *data = nullptr;
    if ( data ) { return data; }

    if ( qApp )
    {
        auto value = qApp->property( "WTHttpRuntimeData" );
        if ( value.isValid() && value.canConvert< void * >() )
        {
            data = reinterpret_cast< WTHttpRuntimeData * >( value.value< void * >() );

            if ( data )
            {
                return data;
            }
        }
    }

    data = new WTHttpRuntimeData;

    if ( qApp )
    {
        qApp->setProperty( "WTHttpRuntimeData", QVariant::fromValue< void * >( data ) );
    }

    return data;
}

void WTHttp::setQmlEngine(QQmlEngine *qmlEngine)
{
    runtimeData()->engine = qmlEngine;
}

QQmlEngine *WTHttp::qmlEngine()
{
    return runtimeData()->engine;
}

QNetworkAccessManager *WTHttp::networkAccessManager()
{
    QMutexLocker locker( &runtimeData()->mutex );

    auto it = runtimeData()->networkAccessManagerMap.find( QThread::currentThread() );

    if ( it == runtimeData()->networkAccessManagerMap.end() )
    {
        QPointer< QNetworkAccessManager > manager( new QNetworkAccessManager );
        manager->setProxy(QNetworkProxy::NoProxy);
        runtimeData()->networkAccessManagerMap[ QThread::currentThread() ] = manager;

        return manager;
    }
    else
    {
        return *it;
    }
}

QNetworkRequest WTHttp::defaultNetworkRequest(const QString &url,bool json)
{
    QNetworkRequest result;
    //设置SSL,HTTPS协议需要SSL证书
    QSslConfiguration m_sslConfig = QSslConfiguration::defaultConfiguration();
    m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
    m_sslConfig.setProtocol(QSsl::TlsV1_2);
    result.setSslConfiguration(m_sslConfig);
    if(json)
    {
        result.setRawHeader( "Content-Type", "application/json;charset=UTF-8" );
    }
    result.setUrl( url );

    return result;
}

QPair< bool, QByteArray > WTHttp::get(
    const QNetworkRequest &nRequest )
{
    QEventLoop                eventLoop;
    QPair< bool, QByteArray > reply = { false, { } };

    auto request = WTHttpRequest::create();

    request->onSucceed(
        [ & ](){
            reply.first = true;
            reply.second = request->responseData().toByteArray();
            eventLoop.quit();
        } );
    request->onError(
        [ & ](){
            reply.second = request->errorMessage().toUtf8();
            eventLoop.quit();
        } );
    request->get( nRequest );

    eventLoop.exec( QEventLoop::ExcludeUserInputEvents );

    return reply;
}

QPair< bool, QByteArray > WTHttp::get(
    const QString & url )
{
    QEventLoop                eventLoop;
    QPair< bool, QByteArray > reply = { false, { } };

    auto request = WTHttpRequest::create();

    request->onSucceed(
        [ & ](){
            reply.first = true;
            reply.second = request->responseData().toByteArray();
            eventLoop.quit();
        } );
    request->onError(
        [ & ](){
            reply.second = request->errorMessage().toUtf8();
            eventLoop.quit();
        } );
    request->get( url );

    eventLoop.exec( QEventLoop::ExcludeUserInputEvents );

    return reply;
}

QPair< bool, QByteArray > WTHttp::post(
    const QNetworkRequest &nRequest,
    const QVariant &       data )
{
    QEventLoop                eventLoop;
    QPair< bool, QByteArray > reply = { false, { } };

    auto request = WTHttpRequest::create();

    request->onSucceed(
        [ & ](){
            reply.first = true;
            reply.second = request->responseData().toByteArray();
            eventLoop.quit();
        } );
    request->onError(
        [ & ](){
            reply.second = request->errorMessage().toUtf8();
            eventLoop.quit();
        } );
    request->post( nRequest, data );

    eventLoop.exec( QEventLoop::ExcludeUserInputEvents );

    return reply;
}

QPair< bool, QByteArray > WTHttp::post(
    const QString & url,
    const QVariant &data )
{
    QEventLoop                eventLoop;
    QPair< bool, QByteArray > reply = { false, { } };

    auto request = WTHttpRequest::create();

    request->onSucceed(
        [ & ](){
            reply.first = true;
            reply.second = request->responseData().toByteArray();
            eventLoop.quit();
        } );
    request->onError(
        [ & ](){
            reply.second = request->errorMessage().toUtf8();
            eventLoop.quit();
        } );
    request->post( url, data );

    eventLoop.exec( QEventLoop::ExcludeUserInputEvents );

    return reply;
}

WTHttpRequest *WTHttp::createRequest()
{
    return WTHttpRequest::create();
}
